package cn.rocksolid.sport.assist.service.impl;

import cn.rocksolid.sport.assist.config.ConfigConst;
import cn.rocksolid.sport.assist.config.OssConfig;
import cn.rocksolid.sport.assist.dao.OssDao;
import cn.rocksolid.sport.assist.entity.OssEntity;
import cn.rocksolid.sport.assist.service.OssService;
import cn.rocksolid.sport.common.Constant.OssPt;
import cn.rocksolid.sport.common.error.RSE;
import cn.rocksolid.sport.common.error.RockSolidException;
import cn.rocksolid.sport.common.utils.AssertUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.qcloud.Module.Sts;
import com.qcloud.QcloudApiModuleCenter;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.*;

@Service
public class OssServiceImpl extends ServiceImpl<OssDao, OssEntity> implements OssService {

  private static final String DT_PATTERN = "yyyyMMdd_HHmmss";
  private static final String UNDERLINE = "_";

  private Map<OssPt, Map<String, OssProvider>> ossProviders;

  /**
   * Below is service providers initial method.
   */
  @Autowired
  public OssServiceImpl(final OssConfig ossConfig) {
    Map<OssPt, Map<String, Map<String, String>>> ossConfigs = ossConfig.getOssConfigs();
    if (MapUtils.isNotEmpty(ossConfigs)) {
      ossProviders = new EnumMap<>(OssPt.class);
      for (Map.Entry<OssPt, Map<String, Map<String, String>>> typeOfCloudOssConfigs : ossConfigs.entrySet()) {
        if (OssPt.TENCENT == typeOfCloudOssConfigs.getKey()) {
          Map<String, OssProvider> tencentCloudOssProviders = new HashMap<>();
          for (Map.Entry<String, Map<String, String>> config : typeOfCloudOssConfigs.getValue().entrySet()) {
            tencentCloudOssProviders.put(config.getKey(), new TencentOssProvider(config.getValue()));
          }
          ossProviders.put(OssPt.TENCENT, tencentCloudOssProviders);
        } else {
          throw new RockSolidException(RSE.INVALID_OSSP_CONF);
        }
      }
    }
  }

  @Override
  public OssEntity credential(final OssEntity ossEntity) {
    validatePreSave(ossEntity);
    OssProvider cloudOssProvider = findOutEffectiveProvider(ossEntity);
    cloudOssProvider.resolve(ossEntity);
    ossEntity.setPayload(cloudOssProvider.credential());
    return ossEntity;
  }

  private void validatePreSave(final OssEntity ossEntity) {
    AssertUtils.isNotNull(ossEntity, RSE.INVALID_PAYLOAD);
    AssertUtils.isUuid32(ossEntity.getId(), RSE.INVALID_OSSID);
    AssertUtils.isAlphanum(ossEntity.getMethod(), 3, 4, RSE.INVALID_OSSUTYPE);
  }

  private OssProvider findOutEffectiveProvider(final OssEntity ossEntity) {
    if (null == ossEntity.getPt()) {
      ossEntity.setPt(OssPt.TENCENT);
    }
    if (StringUtils.isBlank(ossEntity.getConfKey())) {
      ossEntity.setConfKey(ConfigConst.DEFAULT_CONF);
    }
    return ossProviders.get(ossEntity.getPt()).get(ossEntity.getConfKey());
  }

  private static class TencentOssProvider extends AbstractOssProvider implements OssProvider {

    private final TreeMap<String, Object> defaultGenTempSecretConf;

    private final String prefixOfOssObjectDomain;

    private final String bucketName;

    private TencentOssProvider(final Map<String, String> config) {
      super(config);
      try {
        defaultGenTempSecretConf = new TreeMap<>();
        defaultGenTempSecretConf.put("SecretId", config.get("secret-id"));
        defaultGenTempSecretConf.put("SecretKey", config.get("secret-key"));
        defaultGenTempSecretConf.put(TencentCloudSts.K_DURATION_IN_SECONDS, 60);

        this.bucketName = StringUtils.trim(config.get("bucket-name")) + "-" + StringUtils.trim(config.get("app-id"));

        this.prefixOfOssObjectDomain =
                StringUtils.trim(config.get("protocol")) + "://" + this.bucketName + ".cos." + config.get("region") +
                ".myqcloud.com";
      } catch (Exception exp) {
        throw new RockSolidException(RSE.OSSP_INIT_FAIL, exp);
      }
    }

    @Override
    public void resolve(final OssEntity ossEntity) {
      SimpleDateFormat df = new SimpleDateFormat(DT_PATTERN);
      ossEntity.setConverted(
              "/" + df.format(new Date()) + UNDERLINE + ossEntity.getLength() + UNDERLINE + ossEntity.getId() +
              FilenameUtils.EXTENSION_SEPARATOR_STR +
              StringUtils.trimToEmpty(FilenameUtils.getExtension(ossEntity.getOrigin())));
      ossEntity.setUrl(prefixOfOssObjectDomain + ossEntity.getConverted());
    }

    @Override
    public Map<String, String> credential() {
      Map<String, String> ossProviderConfig = new HashMap<>();
      ossProviderConfig.put("bucketName", this.bucketName);
      ossProviderConfig.put("region", this.getConfig().get("region"));
      ossProviderConfig.put("tempSecretCredential", TencentCloudSts.getCredential(defaultGenTempSecretConf));
      return ossProviderConfig;
    }
  }

  private static interface OssProvider {
    Map<String, String> getConfig();

    void resolve(final OssEntity ossEntity);

    Map<String, String> credential();
  }

  private abstract static class AbstractOssProvider {

    protected Map<String, String> config;

    private AbstractOssProvider(final Map<String, String> config) {
      if (MapUtils.isEmpty(config)) {
        throw new RockSolidException(RSE.INVALID_OSSP_CONF);
      }
      this.config = config;
    }

    public Map<String, String> getConfig() {
      return config;
    }
  }

  private static final class TencentCloudSts {

    private static final String POLICY =
            "{\"statement\": [{\"action\": [\"name/cos:*\"],\"effect\": \"allow\",\"resource\":\"*\"}],\"version\": \"2.0\"}";
    private static final int DEFAULT_DURATION_IN_SECONDS = 1800;

    private static final String K_POLICY = "policy";
    private static final String K_DURATION_IN_SECONDS = "durationInSeconds";

    public static String getCredential(final TreeMap<String, Object> config) {
      config.put("RequestMethod", "GET");

      QcloudApiModuleCenter module = new QcloudApiModuleCenter(new Sts(), config);

      TreeMap<String, Object> params = new TreeMap<>();

      params.put("name", "tac-storage-sts-java");
      String policy = config.get(K_POLICY) == null ? POLICY : (String) config.get(K_POLICY);
      params.put(K_POLICY, policy);
      int durationInSeconds = config.get(K_DURATION_IN_SECONDS) == null ? DEFAULT_DURATION_IN_SECONDS :
              (Integer) config.get(K_DURATION_IN_SECONDS);
      params.put(K_DURATION_IN_SECONDS, durationInSeconds);

      try {
        return module.call("GetFederationToken", params);
      } catch (Exception exp) {
        throw new RockSolidException(RSE.OSSP_INIT_FAIL);
      }
    }
  }
}
